/**
 * 共用模块
 *
 * Created by snowingsea on 2020/01/22.
 */
const fs = require('fs-extra');
const path = require('path');
const randomstring = require('randomstring');
const chai = require('chai');
const should = chai.should(); // eslint-disable-line no-unused-vars
chai.use(require('chai-http'));
chai.use(require('chai-sorted'));
const Administrator = require('./../models/administrator');
const projects = require('./../../phoenix/models/projects');
const projectVersions = require('./../../phoenix/models/project-versions');
const files = require('./../../phoenix/models/files');
const functions = require('./../../phoenix/models/functions');
const fileElements = require('./../../phoenix/models/file-elements');
const license = require('./../../phoenix/models/license');
const utility = require('./../../phoenix/models/utility');
const users = require('./../../phoenix/models/users');
// const { statusKind } = require('./../../phoenix/models/license');

/**
 * chai请求
 * @param {Object} chai
 * @param {String|Function} uri 统一资源标识符
 * @param {String|Function} url 统一资源定位符
 * @param {String} method http方法
 * @param {String|Function} accessToken 访问令牌
 * @param {Object} query 请求参数
 * @return {Promise}
 */
function request(chai, uri, url, method, accessToken, query = {}) {
  return new Promise((resolve, reject) => {
    if (typeof (url) === 'function') { url = url(); }
    if (typeof (uri) === 'function') { uri = uri(); }
    if (typeof (accessToken) === 'function') { accessToken = accessToken(); }
    const auth = {
      key: 'key',
      value: 'value',
    };
    if (accessToken) {
      auth.key = 'Authorization';
      auth.value = `Bearer ${accessToken}`;
    }
    switch (method.toLowerCase()) {
      case 'get':
        chai.request(uri)
          .get(url)
          .set(auth.key, auth.value)
          .query(query)
          .end((err, res) => {
            err ? reject(err) : resolve(res);
          });
        break;
      case 'post':
        chai.request(uri)
          .post(url)
          .set(auth.key, auth.value)
          .query(query)
          .end((err, res) => {
            err ? reject(err) : resolve(res);
          });
        break;
      case 'put':
        chai.request(uri)
          .put(url)
          .set(auth.key, auth.value)
          .query(query)
          .end((err, res) => {
            err ? reject(err) : resolve(res);
          });
        break;
      case 'patch':
        chai.request(uri)
          .patch(url)
          .set(auth.key, auth.value)
          .query(query)
          .end((err, res) => {
            err ? reject(err) : resolve(res);
          });
        break;
      case 'delete':
        chai.request(uri)
          .delete(url)
          .set(auth.key, auth.value)
          .query(query)
          .end((err, res) => {
            err ? reject(err) : resolve(res);
          });
        break;
      default:
        reject(new Error(`Unknown method: ${method}`));
        break;
    }
  });
}

/**
 * 判断response信息为错误信息类型
 * @param {Object} message
 */
function shouldBeErrorMessage(message) {
  message.should.be.a('object');
  message.should.have.property('request');
  message.request.should.be.a('string');
  message.should.have.property('message');
  message.message.should.be.a('string');
  message.should.have.property('description');
  message.description.should.be.a('string');
}

/**
 * 判断response信息为ping信息类型
 * @param {Object} message
 */
function shouldBePingMessage(message) {
  message.should.be.a('object');
  message.should.have.property('message');
  message.message.should.be.eql('pong');
  message.should.have.property('timestamp');
  message.timestamp.should.be.a('number');
}

/**
 * 判断response信息为机器码信息类型
 * @param message
 */
function shouldBeMachineCodeMessage(message) {
  message.should.be.a('object');
  message.should.have.property('machineCode');
  message.machineCode.should.be.a('string');
}

/**
 * 判断response信息为许可证信息类型
 * @param message
 */
function shouldBeLicenseMessage(message) {
  message.should.be.a('object');
  message.should.have.property('status');
  message.status.should.be.a('number');
  if ((message.status & license.statusKind.illegal) > 0) { // eslint-disable-line no-bitwise
    message.should.not.have.property('license');
    message.status.should.be.eql(license.statusKind.illegal);
    return;
  }
  if ((message.status & license.statusKind.notMatch) > 0) { // eslint-disable-line no-bitwise
    message.should.not.have.property('license');
    message.status.should.be.eql(license.statusKind.notMatch);
    return;
  }
  message.should.have.property('license');
  message.license.should.be.a('object');
  message.license.should.have.property('companyName');
  message.license.companyName.should.be.a('string');
  message.license.should.have.property('expiresDate');
  message.license.expiresDate.should.be.a('string');
  message.license.should.have.property('cpuLimit');
  message.license.cpuLimit.should.be.a('number');
  message.license.should.have.property('userLimit');
  message.license.userLimit.should.be.a('number');
  const expiresTimestamp = new Date(message.license.expiresDate).getTime();
  if ((message.status & license.statusKind.exceedTime) > 0) { // eslint-disable-line no-bitwise
    expiresTimestamp.should.be.below(new Date().getTime());
  } else {
    expiresTimestamp.should.be.above(new Date().getTime());
  }
}

/**
 * 判断response信息为token信息类型
 * @param {Object} message
 */
function shouldBeTokenMessage(message) {
  message.should.be.a('object');
  message.should.have.property('accessToken');
  message.accessToken.should.be.a('string');
  message.should.have.property('expiresIn');
  message.expiresIn.should.be.a('number');
  message.should.have.property('refreshToken');
  message.refreshToken.should.be.a('string');
}

/**
 * 判断response信息为用户信息类型
 * @param {Object} message
 */
function shouldBeUserMessage(message) {
  message.should.be.a('object');
  message.should.have.property('userId');
  message.userId.should.be.a('string');
  message.should.have.property('username');
  message.username.should.be.a('string');
  message.should.have.property('nickname');
  message.nickname.should.be.a('string');
  message.should.have.property('role');
  message.role.should.be.a('number');
  message.should.have.property('status');
  message.status.should.be.a('number');
  message.should.have.property('lang');
  message.lang.should.be.a('string');
}

/**
 * 判断response信息为用户列表类型
 * @param {Object} message
 */
function shouldBeUsersMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('users');
  message.users.should.be.a('array');
  message.users.forEach((user) => {
    shouldBeUserMessage(user);
  });
}

function shouldCompilationOptionsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('version');
  message.version.should.be.a('string');
  message.should.have.property('environments');
  message.environments.should.be.a('object');
  message.should.have.property('languages');
  message.languages.should.be.a('object');
  // message.should.have.property('systems');
  // message.systems.should.be.a('object');
  message.should.have.property('project types');
  message['project types'].should.be.a('object');
  Object.keys(message['project types']).forEach((type) => {
    message['project types'][type].should.be.a('object');
    message['project types'][type].should.have.property('languages');
    message['project types'][type].should.have.property('environment');
    // message['project types'][type].should.have.property('system');
  });
  // Object.keys(message.systems).forEach((system) => {
  //   message.systems[system].should.be.a('object');
  // });
  Object.keys(message.environments).forEach((environment) => {
    message.environments[environment].should.be.a('object');
  });
}

/**
 * 判断response信息为文件概要信息类型
 * @param {Object} message
 */
function shouldBeFileSummaryMessage(message) {
  message.should.be.a('object');
  message.should.have.property('fileId');
  message.fileId.should.be.a('string');
  message.should.have.property('name');
  message.fileName.should.be.a('string');
  message.should.have.property('path');
  message.path.should.be.a('string');
  message.fileName.should.be.eql(path.basename(message.path));
  message.should.have.property('status');
  message.status.should.be.a('number');
  message.should.have.property('kind');
  message.kind.should.be.a('number');
  if (message.statementCoverage) {
    message.statementCoverage.should.be.a('number');
  }
  if (message.branchCoverage) {
    message.branchCoverage.should.be.a('number');
  }
  if (message.mcdcCoverage) {
    message.mcdcCoverage.should.be.a('number');
  }
  if (message.failed) {
    message.failed.should.be.a('boolean');
  }
}

/**
 * 判断response信息为项目版本概要信息类型
 * @param {Object} message
 */
function shouldBeProjectVersionSummaryMessage(message) {
  message.should.be.a('object');
  message.should.have.property('versionId');
  message.versionId.should.be.a('string');
  message.should.have.property('versionName');
  message.versionName.should.be.a('string');
  message.should.have.property('createAt');
  message.createAt.should.be.a('number');
  message.should.have.property('visitAt');
  message.visitAt.should.be.a('number');
  message.should.have.property('rootDirectory');
  message.rootDirectory.should.be.a('object');
  shouldBeFileSummaryMessage(message.rootDirectory);
  if (message.status === projectVersions.statusKind.frozen) {
    message.should.have.property('finishAt');
    message.finishAt.should.be.a('number');
    message.finishAt.should.above(message.createAt);
  } else {
    message.should.not.have.property('finishAt');
  }
}

/**
 * 判断response信息为项目概要信息类型
 * @param {Object} message
 */
function shouldBeProjectSummaryMessage(message) {
  message.should.be.a('object');
  message.should.have.property('projectId');
  message.projectId.should.be.a('string');
  message.should.have.property('projectName');
  message.projectName.should.be.a('string');
  message.should.have.property('createAt');
  message.createAt.should.be.a('number');
  message.should.have.property('visitAt');
  message.visitAt.should.be.a('number');
  message.should.have.property('status');
  message.status.should.be.a('number');
  if (message.status === projects.statusKind.frozen) {
    message.should.have.property('finishAt');
    message.finishAt.should.be.a('number');
    message.finishAt.should.above(message.createAt);
  } else {
    message.should.not.have.property('finishAt');
  }
  if (message.lastVersion) {
    message.lastVersion.should.be.a('object');
    shouldBeProjectVersionSummaryMessage(message.lastVersion);
  }
}

/**
 * 判断response信息为函数概要信息类型
 * @param {Object} message
 */
function shouldBeFunctionSummaryMessage(message) {
  message.should.be.a('object');
  message.should.have.property('id');
  message.id.should.be.a('string');
  message.should.have.property('parentId');
  message.parentId.should.be.a('string');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('fileId');
  message.fileId.should.be.a('string');
  message.should.have.property('name');
  message.name.should.be.a('string');
  message.should.have.property('fullName');
  message.fullName.should.be.a('string');
  message.should.have.property('kind');
  message.kind.should.be.a('number');

  if (message.statementCoverage) {
    message.statementCoverage.should.be.a('number');
  }
  if (message.branchCoverage) {
    message.branchCoverage.should.be.a('number');
  }
  if (message.mcdcCoverage) {
    message.mcdcCoverage.should.be.a('number');
  }
  if (message.failed) {
    message.failed.should.be.a('boolean');
  }
  if (message.description) {
    message.description.should.be.a('string');
  }
  if (message.location) {
    message.location.should.be.a('object');
    message.location.should.have.property('line');
    message.location.should.have.property('column');
  }
  if (message.typeTemplateParms) {
    message.typeTemplateParms.should.be.a('array');
    message.typeTemplateParms[0].should.have.property('name');
    message.typeTemplateParms[0].name.should.be.a('string');
  }
  if (message.noneTypeTemplateParms) {
    message.noneTypeTemplateParms.should.be.a('array');
    message.noneTypeTemplateParms[0].should.have.property('name');
    message.noneTypeTemplateParms[0].name.should.be.a('string');
    message.noneTypeTemplateParms[0].should.have.property('type');
    message.noneTypeTemplateParms[0].type.should.be.a('string');
  }
  if (message.templateTemplateParms) {
    message.templateTemplateParms.should.be.a('array');
    message.templateTemplateParms[0].should.have.property('name');
    message.templateTemplateParms[0].name.should.be.a('string');
    message.templateTemplateParms[0].should.have.property('templatePrefix');
    message.templateTemplateParms[0].templatePrefix.should.be.a('string');
  }
}

/**
 * 判断response信息为项目类型
 * @param {Object} message
 */
function shouldBeProjectMessage(message) {
  shouldBeProjectSummaryMessage(message);
  message.should.have.property('description');
  message.description.should.be.a('string');
}

/**
 * 判断response信息为项目列表类型
 * @param {Object} message
 */
function shouldBeProjectsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('projects');
  message.projects.should.be.a('array');
  message.projects.forEach((project) => {
    shouldBeProjectSummaryMessage(project);
  });
}

/**
 * 判断response信息为项目版本类型
 * @param message
 */
function shouldBeProjectVersionMessage(message) {
  shouldBeProjectVersionSummaryMessage(message);
  message.should.have.property('projectType');
  message.projectType.should.be.a('string');
  message.should.have.property('language');
  message.language.should.be.a('string');
  message.should.have.property('environment');
  message.environment.should.be.a('string');
  message.should.have.property('headers');
  message.headers.suffixes.should.be.a('array');
  message.headers.suffixes.forEach((suffix) => {
    suffix.should.be.a('string');
  });
  message.should.have.property('languages');
  message.languages[message.language].suffixes.should.be.a('array');
  message.languages[message.language].suffixes.forEach((suffix) => {
    suffix.should.be.a('string');
  });
  message.should.have.property('includes');
  message.includes.should.be.a('array');
  message.includes.forEach((include) => {
    include.should.be.a('string');
  });
  message.should.have.property('macros');
  message.macros.should.be.a('array');
  message.macros.forEach((macro) => {
    macro.should.be.a('object');
    macro.should.have.property('key');
    macro.key.should.be.a('string');
    if (macro.value) {
      macro.value.should.be.a('string');
    }
  });
  if (message.extraExit) {
    message.extraExit.should.be.a('string');
  }
  message.should.have.property('caseSensitive');
  message.caseSensitive.should.be.a('boolean');
  message.should.have.property('systemType');
  message.systemType.should.be.a('number');
}

/**
 * 判断response信息为项目版本统计类型
 * @param {Object} message
 */
function shouldBeProjectVersionStatisticsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('function');
  message.function.should.have.property('total');
  message.function.total.should.be.a('number');
  message.should.have.property('file');
  message.file.should.have.property('total');
  message.file.total.should.be.a('number');
  message.file.should.have.property('headers');
  message.file.headers.should.have.property('total');
  message.file.headers.total.should.be.a('number');
  message.file.should.have.property('languages');
  message.file.languages.should.have.property('c');
  message.file.languages.c.should.be.a('object');
  message.file.languages.c.should.have.property('total');
  message.file.languages.c.total.should.have.property('number');
  message.file.should.have.property('notCompiledCount');
  message.file.notCompiledCount.should.have.property('number');
  message.file.should.have.property('compileFailedCount');
  message.file.compileFailedCount.should.have.property('number');
  message.file.should.have.property('compiledCount');
  message.file.compiledCount.should.have.property('number');
}

/**
 * 判断response信息为项目版本列表类型
 * @param {Object} message
 */
function shouldBeProjectVersionsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('versions');
  message.versions.should.be.a('array');
  message.versions.forEach((version) => {
    shouldBeProjectVersionSummaryMessage(version);
  });
}

function shouldBeProjectTypesMessage(message) {
  message.should.be.a('object');
  Object.keys(message).forEach((key) => {
    message[key].should.be.a('object');
    message[key].should.have.property('default');
    message[key].default.should.be.a('object');
    message[key].should.have.property('environments');
    message[key].environments.should.be.a('object');
    message[key].should.have.property('languages');
    message[key].languages.should.be.a('array');
  });
}
/**
 * 判断response信息为文件类型
 * @param message
 */
function shouldBeFileMessage(message) {
  shouldBeFileSummaryMessage(message);
  if (message.extra) {
    message.extra.should.be.a('number');
  }
  if (message.repository) {
    message.repository.should.be.a('object');
    if (message.repository.type) {
      message.repository.type.should.be.a('number');
      message.repository.should.have.property('url');
      message.repository.url.should.be.a('string');
    }
    if (message.repository.branch) {
      message.repository.branch.should.be.a('string');
    }
    if (message.repository.username) {
      message.repository.username.should.be.a('string');
    }
  }
}

/**
 * 判断response信息为文件树类型
 * @param message
 */
function shouldBeFileTreeMessage(message) {
  message.should.be.a('object');
  message.should.have.property('total');
  message.total.should.be.a('number');
  message.should.have.property('tree');
  message.tree.should.be.an('object');
  let total = 0;
  const fileList = [message.tree];
  while (fileList.length > 0) {
    const file = fileList.pop();
    total += 1;
    shouldBeFileSummaryMessage(file);
    if (file.children && file.children.length > 0) {
      // TODO now file also have children(file elements)
      file.kind.should.be.eql(files.kind.directory);
      file.children.forEach((child) => {
        path.normalize(file.path).should.be.eql(path.normalize(path.dirname(child.path)));
        fileList.push(child);
      });
    }
  }
  message.total.should.be.eql(total);
}

/**
 * 判断response信息为完整文件树类型
 * @param message
 */
function shouldBeFileTreeAllMessage(message) {
  message.should.be.a('object');
  message.should.have.property('total');
  message.total.should.be.a('number');
  message.should.have.property('tree');
  message.tree.should.be.an('object');
  let total = 0;
  const fileList = [message.tree];
  while (fileList.length > 0) {
    const file = fileList.pop();
    total += 1;
    if (file.children && file.children.length > 0) {
      file.children.forEach((child) => {
        if (child.kind === files.kind.directory || child.kind === files.kind.file) {
          shouldBeFileSummaryMessage(file);
          path.normalize(file.path).should.be.eql(path.normalize(path.dirname(child.path)));
        }
        fileList.push(child);
      });
    }
  }
  message.total.should.be.eql(total);
}

/**
 * 判断response信息为子目录类型
 * @param message
 */
function shouldBeFileChildrenMessage(message) {
  message.should.be.a('object');
  message.should.have.property('total');
  message.total.should.be.a('number');
  message.should.have.property('files');
  message.files.should.be.an('array');
  message.files.length.should.be.eql(message.total);
  if (message.files.length > 0) {
    const dir = path.normalize(path.dirname(message.files[0].path));
    message.files.forEach((file) => {
      shouldBeFileSummaryMessage(file);
      dir.should.be.eql(path.normalize(path.dirname(file.path)));
    });
  }
}

/**
 * 判断信息为非规范类型
 * @param type
 */
function shouldBeNonCanonicalTypeMessage(type) {
  type.should.be.a('object');
  type.should.have.property('@canonical');
  type['@canonical'].should.be.a('string');
  // type.should.have.property('@unqualified');
  // type['@unqualified'].should.be.a('string');
}

/**
 * 判断信息为数组类型
 * @param type
 */
function shouldBeArrayTypeMessage(type) {
  type.should.be.a('object');
  type.should.have.property('@elementType');
  type['@elementType'].should.be.a('string');
  type.should.have.property('@length');
  type['@length'].should.be.a('string');
}

/**
 * 判断信息为指针类型
 * @param type
 */
function shouldBePointerTypeMessage(type) {
  type.should.be.a('object');
  type.should.have.property('@pointeeType');
  type['@pointeeType'].should.be.a('string');
}

/**
 * 判断信息为结构类型
 * @param type
 */
function shouldBeRecordTypeMessage(type) {
  type.should.be.a('object');
  if (type['@fields']) {
    type['@fields'].should.be.a('object');
    // Object.keys(type['@fields']).length.should.be.above(0);
    Object.keys(type['@fields']).forEach((name) => {
      const field = type['@fields'][name];
      field.should.be.a('object');
      field.should.have.property('@type');
      field['@type'].should.be.a('string');
    });
  }
}

/**
 * 判断信息为函数类型
 * @param type
 */
function shouldBeFunctionTypeMessage(type) {
  type.should.be.a('object');
  if (type['%']) {
    type['%'].should.be.a('object');
    type['%'].should.have.property('@type');
    type['%']['@type'].should.be.a('string');
  }
  if (type.params) {
    type.params.should.be.a('array');
    type.params.forEach((param) => {
      param.should.be.a('object');
      param.should.have.property('@type');
      param['@type'].should.be.a('string');
      param.should.have.property('@name');
      param['@name'].should.be.a('string');
    });
  }
}

/**
 * 判断response信息为类型系统类型
 * @param message
 */
function shouldBeFileTypesMessage(message) {
  message.should.be.a('object');
  message.should.have.property('fileId');
  message.fileId.should.be.a('string');
  message.should.have.property('types');
  message.types.should.be.a('object');
  Object.keys(message.types).forEach((typeName) => {
    const type = message.types[typeName];
    type.should.be.a('object');
    type.should.have.property('@attributes');
    type['@attributes'].should.be.a('array');
    if (type['@attributes'].indexOf('isNonCanonical') >= 0) {
      shouldBeNonCanonicalTypeMessage(type);
      return;
    }
    if (type['@attributes'].indexOf('isConst') >= 0) {
      type.should.have.property('@unqualified');
      type['@unqualified'].should.be.a('string');
      return;
    }

    type['@attributes'].forEach((attribute) => {
      switch (attribute) {
        case 'isArray':
          shouldBeArrayTypeMessage(type);
          break;
        case 'isPointer':
          shouldBePointerTypeMessage(type);
          break;
        case 'isRecord':
          shouldBeRecordTypeMessage(type);
          break;
        case 'isFunction':
          shouldBeFunctionTypeMessage(type);
          break;
        case 'isConst':
        case 'isVoid':
        case 'isStructure':
        case 'isUnion':
        case 'isEnumeral':
        case 'isChar':
        case 'isInteger':
        case 'isFloating':
        case 'isReal':
        case 'isVariadic':
        case 'isFunctionPointer':
        case 'isVoidPointer':
        case 'isAnonymous':
        case 'isIncomplete':
        default:
          // TODO: 待细化
          break;
      }
    });
  });
}

/**
 * 判断response信息为文件全局变量类型
 * @param {Object} message
 */
function shouldBeFileGlobalsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('fileId');
  message.fileId.should.be.a('string');
  message.should.have.property('globals');
  message.globals.should.be.a('object');
  Object.keys(message.globals).forEach((varName) => {
    const variable = message.globals[varName];
    variable.should.be.a('object');
    variable.should.have.property('@type');
    variable['@type'].should.be.a('string');
    if (variable['@expr']) {
      variable['@expr'].should.be.a('string');
    }
    // TODO: 待细化
  });
}

/**
 * 判断response信息为文件字面量类型
 * @param {Object} message
 */
function shouldBeFileLiteralsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('fileId');
  message.fileId.should.be.a('string');
  message.should.have.property('literals');
  message.literals.should.be.a('array');
  message.literals.forEach((item) => {
    item.should.be.a('object');
    item.should.have.property('@type');
    item['@type'].should.be.a('string');
    // TODO: 待细化
  });
}

/**
 * 判断response信息为函数信息
 * @param {Object} message
 */
function shouldBeFunctionMessage(message) {
  shouldBeFunctionSummaryMessage(message);
}

/**
 * 判断response信息为函数列表信息
 * @param {Object} message
 */
function shouldBeFunctionsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('functions');
  message.functions.should.be.a('array');
  message.functions.length.should.be.eql(message.pagination.total);
  message.functions.forEach((func) => {
    shouldBeFunctionSummaryMessage(func);
  });
}

/**
 * 判断response信息为文件元素列表信息
 * @param {Object} message
 */
function shouldBeElementsMessage(message) {
  message.should.be.a('object');
  message.should.have.property('total');
  message.total.should.be.a('number');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.should.have.property('elements');
  message.elements.should.be.a('array');
  message.elements.length.should.be.eql(message.total);
  message.elements.forEach((element) => {
    shouldBeFunctionSummaryMessage(element);
  });
}

/**
 * 判断response信息为元素树类型
 * @param message
 */
function shouldBeElementsTreeMessage(message) {
  message.should.be.a('object');
  message.should.have.property('total');
  message.total.should.be.a('number');
  message.should.have.property('tree');
  message.tree.should.be.an('object');
  let total = 0;
  const elementList = [message.tree];
  while (elementList.length > 0) {
    const element = elementList.pop();
    total += 1;
    shouldBeFunctionSummaryMessage(element);
    if (element.children && element.children.length > 0) {
      element.children.forEach((child) => {
        elementList.push(child);
      });
    }
  }
  message.total.should.be.eql(total);
}

/**
 * 判断response信息为特化元素
 * @param message
 */
function shouldBeSpecializedElementMessage(message) {
  shouldBeFunctionSummaryMessage(message);
  message.kind.should.be.oneOf(
    [
      fileElements.elementKind.specialFunction,
      fileElements.elementKind.specialStruct,
      fileElements.elementKind.specialClass,
    ]
  );
}

/**
 * 判断response信息为函数CFG信息
 * @param {Object} message
 */
function shouldBeFunctionCFGMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('nodes');
  message.nodes.should.be.a('array');
  message.nodes.forEach((node) => {
    node.should.be.a('object');
    node.should.have.property('nodeId');
    node.nodeId.should.be.a('number');
    node.should.have.property('kind');
    node.kind.should.be.a('number');
    node.should.have.property('label');
    node.label.should.be.a('string');
    node.should.have.property('begin');
    node.begin.should.be.a('object');
    node.begin.should.have.property('line');
    node.begin.line.should.be.a('number');
    node.begin.should.have.property('column');
    node.begin.column.should.be.a('number');
    node.should.have.property('end');
    node.end.should.be.a('object');
    node.end.should.have.property('line');
    node.end.line.should.be.a('number');
    node.end.should.have.property('column');
    node.end.column.should.be.a('number');
    if (node.decisions) {
      node.kind.should.be.oneOf(Object.keys(functions.cfgNodeKind).map((keyName) => functions.cfgNodeKind[keyName]));
      node.decisions.should.be.a('array');
      node.decisions.forEach((decision) => {
        decision.should.have.property('expr');
        decision.expr.should.be.a('string');
        decision.should.have.property('conditions');
        decision.conditions.should.be.a('object');
        decision.should.have.property('cases');
        decision.cases.should.be.a('array');
        decision.cases.forEach((itemCase) => {
          itemCase.should.have.property('return');
          itemCase.return.should.be.a('boolean');
          itemCase.should.have.property('conditions');
          itemCase.conditions.should.be.a('array');
          itemCase.conditions.forEach((condition) => {
            condition.should.have.property('id');
            condition.id.should.be.a('number');
            decision.conditions.should.have.property(condition.id.toString());
            decision.conditions[condition.id.toString()].should.be.a('string');
            condition.should.have.property('value');
            condition.value.should.be.a('boolean');
          });
        });
        if (decision.testIds) {
          decision.testIds.should.be.a('array');
          decision.testIds.forEach((testId) => {
            testId.should.be.a('string');
          });
        }
      });
    }
  });
  message.should.have.property('edges');
  message.edges.should.be.a('array');
  message.edges.forEach((edge) => {
    edge.should.have.property('startNodeId');
    edge.startNodeId.should.be.a('number');
    edge.should.have.property('endNodeId');
    edge.endNodeId.should.be.a('number');
    edge.should.have.property('label');
    edge.label.should.be.a('string');
  });
}

/**
 * 判断response信息为变量集合信息
 * @param {Object} message
 */
function shouldBeVariablesMessage(message) {
  if (message.variables) {
    message.variables.should.be.a('object');
    if (message.variables.global) {
      message.variables.global.should.be.a('object');
      Object.keys(message.variables.global).forEach((name) => {
        message.variables.global[name].should.be.a('object');
        message.variables.global[name].should.have.property('@type');
        message.variables.global[name]['@type'].should.be.a('string');
      });
    }
    if (message.variables.params) {
      message.variables.params.should.be.a('object');
      Object.keys(message.variables.params).forEach((name) => {
        message.variables.params[name].should.be.a('object');
        message.variables.params[name].should.have.property('@type');
        message.variables.params[name]['@type'].should.be.a('string');
      });
    }
    if (message.variables.statics) {
      message.variables.statics.should.be.a('object');
      Object.keys(message.variables.statics).forEach((sta) => {
        message.variables.params[sta].should.have.property('@name');
        message.variables.params[sta]['@name'].should.be.a('string');
        message.variables.params[sta].should.have.property('location');
        message.variables.params[sta].location.should.be.a('object');
        message.variables.params[sta].location.should.have.property('begin');
        message.variables.params[sta].location.should.have.property('end');
        message.variables.params[sta].should.have.property('@type');
        message.variables.params[sta]['@type'].should.be.a('string');
      });
    }
  }
  if (message.stubs) {
    message.stubs.should.be.a('object');
    Object.keys(message.stubs).forEach((stubName) => {
      const stub = message.stubs[stubName];
      if (stub['%']) {
        stub['%'].should.be.a('object');
        stub['%'].should.have.property('@type');
        stub['%']['@type'].should.be.a('string');
      }
      if (stub.params) {
        stub.params.should.be.a('array');
        stub.params.forEach((param) => {
          param.should.be.a('object');
          param.should.have.property('@type');
          param['@type'].should.be.a('string');
          if (param['@name']) {
            param['@name'].should.be.a('string');
          }
        });
      }
    });
  }
  if (message.malloc) {
    message.malloc.should.be.a('object');
    Object.keys(message.malloc).forEach((name) => {
      const variable = message.malloc[name];
      variable.should.be.a('object');
      if (variable['@type']) {
        variable['@type'].should.be.a('string');
      } else {
        if (variable['@type'] !== null) {
          variable.should.not.have.property('@type');
        }
        variable.should.have.property('@elementType');
        variable['@elementType'].should.be.a('string');
        variable.should.have.property('@length');
        variable['@length'].should.be.a('string');
      }
    });
  }
  if (message.output) {
    message.output.should.be.a('object');
    if (message.output['%']) {
      message.output['%'].should.be.a('object');
      message.output['%'].should.have.property('@type');
      message.output['%']['@type'].should.be.a('string');
    }
    if (message.output.global) {
      message.output.global.should.be.a('object');
      Object.keys(message.output.global).forEach((name) => {
        message.output.global[name].should.be.a('object');
        message.output.global[name].should.have.property('@type');
        message.output.global[name]['@type'].should.be.a('string');
      });
    }
  }
}

/**
 * 判断response信息为函数变量信息
 * @param {Object} message
 */
function shouldBeFunctionVariablesMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  shouldBeVariablesMessage(message);
}

/**
 * 判断response信息为桩函数信息
 * @param message
 */
function shouldbeStubMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('stubName');
  message.stubName.should.be.a('string');
  message.should.have.property('return');
  message.return.should.be.a('object');
  message.return.should.have.property('@type');
  message.return['@type'].should.be.a('string');
  message.should.have.property('params');
  message.params.should.be.an('array');
  message.params.forEach((param) => {
    param.should.have.property('@type');
    param['@type'].should.be.a('string');
  });
  if (message.times) {
    message.times.should.be.a('number');
    message.times.should.be.gt(0);
  }
}

/**
 * 判断params为桩函数有效参数信息列表
 * @param message
 */
function shouldbeValidStubParam(params) {
  const paramNames = new Set();
  if (params.length > 0) {
    params.forEach((param) => {
      if (param['@name']) {
        paramNames.add(param['@name']);
      }
    });
    if (paramNames.size !== 0) {
      paramNames.size.should.be.eql(params.length);
    }
  }
}
/**
 *判断response信息为桩函数指针目标信息
 * @param message
 */
function shouldBeStubPointerTargetMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.should.have.property('stubName');
  message.should.have.property('pointerTargetName');
  message.functionId.should.be.a('string');
  if (message.typeName) {
    message.typeName.should.be.a('string');
  } else {
    message.should.have.property('elementTypeName');
    message.elementTypeName.should.be.a('string');
    message.should.have.property('length');
    message.length.should.be.a('number');
  }
}

/**
 * 判断response信息为测试用例概要信息
 * @param {Object} message
 */
function shouldBeTestCaseSummaryMessage(message) {
  message.should.be.a('object');
  message.should.have.property('testId');
  message.testId.should.be.a('string');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('description');
  message.description.should.be.a('string');
  message.should.have.property('kind');
  message.kind.should.be.a('number');
}

/**
 * 判断response信息为测试用例信息
 * @param {Object} message
 */
function shouldBeTestCaseMessage(message) {
  shouldBeTestCaseSummaryMessage(message);
  message.should.have.property('data');
  message.data.should.be.a('object');
  shouldBeVariablesMessage(message.data);
}

/**
 * 判断response信息为测试用例列表导出的信息
 * @param {Object} message
 */
function shouldBeExportTestCaseMessage(message) {
  Object.keys(message).length.should.be.gt(0);
  Object.keys(message).forEach((key) => {
    key.should.be.a('string');
    const part = key.split(':');
    part.length.should.be.eql(2);
    message[key].forEach((testCase) => {
      testCase.should.be.a('object');
      testCase.should.have.property('description');
      testCase.description.should.be.a('string');
      testCase.should.have.property('kind');
      testCase.kind.should.be.a('number');
      testCase.should.have.property('data');
      testCase.data.should.be.a('object');
      shouldBeVariablesMessage(testCase.data);
    });
  });
}

/**
 * 判断response信息为测试用例编组信息
 * @param {Object} message
 */
function shouldBeTestCaseGroupMessage(message) {
  message.should.be.a('object');
  message.should.have.property('type');
  message.type.should.be.eql('group');
  message.should.have.property('groupId');
  message.groupId.should.be.a('string');
}

/**
 * 判断response信息为测试用例列表信息
 * @param {Object} message
 */
function shouldBeTestCasesMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('tests');
  message.tests.should.be.a('array');
  message.pagination.total.should.be.above(message.tests.length - 1);
  message.tests.forEach((testCase) => {
    shouldBeTestCaseSummaryMessage(testCase);
  });
}

/**
 * 判断response信息为函数指针目标信息类型
 * @param {Object} message
 */
function shouldBeFunctionMallocVariableMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('variableName');
  message.variableName.should.be.a('string');
  if (message.elementTypeName) {
    message.elementTypeName.should.be.a('string');
    message.should.have.property('length');
    message.length.should.be.a('number');
    message.should.have.property('typeName');
    message.typeName.should.be.a('string');
  } else {
    message.should.have.property('typeName');
    message.typeName.should.be.a('string');
  }
}

/**
 * 判断response信息为绝对地址信息类型
 * @param {Object} message
 */
function shouldBeFunctionFixedAddressVariableMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functionId');
  message.functionId.should.be.a('string');
  message.should.have.property('fixedAddressName');
  message.fixedAddressName.should.be.a('string');
  if (message.pointerTargets) {
    message.pointerTargets.should.be.a('object');
  }
  if (message.length) {
    message.length.should.be.a('string');
  }
}

/**
 * 判断response信息为任务信息类型
 * @param {Object} message
 */
function shouldBeTaskMessage(message) {
  message.should.be.a('object');
  message.should.have.property('taskId');
  message.taskId.should.be.a('string');
  message.should.have.property('status');
  message.status.should.be.a('number');
  message.should.have.property('type');
  message.type.should.be.a('number');
  message.should.have.property('progress');
  message.progress.should.be.a('number');
}

/**
 * 判断response信息为任务信息列表类型
 * @param {Object} message
 */
function shouldBeTasksMessage(message) {
  message.should.be.a('object');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
  message.should.have.property('tasks');
  message.tasks.should.be.a('array');
  message.tasks.length.should.be.eql(message.pagination.total);
  message.tasks.forEach((task) => {
    shouldBeTaskMessage(task);
  });
}

/**
 * 接口需要用户访问权限
 * @param {String} uri 统一资源标识符
 * @param {String|Function} url 统一资源定位符
 * @param {String} method http方法
 */
function shouldBeUser(uri, url, method) {
  it('it should failed when loss the access token', function (done) {
    request(chai, uri, url, method, null)
      .then((res) => {
        res.should.have.status(401);
        shouldBeErrorMessage(res.body);
        done();
      })
      .catch(done);
  });
  it('it should failed when the access token is invalid', function (done) {
    const accessToken = randomstring.generate(40);
    request(chai, uri, url, method, accessToken)
      .then((res) => {
        res.should.have.status(401);
        shouldBeErrorMessage(res.body);
        done();
      })
      .catch(done);
  });
}

/**
 * 要求接口需要管理员访问权限
 * @param {String} uri 统一资源标识符
 * @param {String|Function} url 统一资源定位符
 * @param {String} method http方法
 */
function shouldBeAdministrator(uri, url, method) {
  it('it should failed when the user is not a administrator', function (done) {
    const administrator = new Administrator();
    let tester;
    let response;
    administrator.createRandomTester()
      .then((user) => {
        tester = user;
        return tester.login();
      })
      .then(() => request(chai, uri, url, method, tester.token.accessToken))
      .then((res) => {
        response = res;
        return tester.logout();
      })
      .then(() => administrator.removeUser(tester.userId))
      .then(() => {
        response.should.have.status(403);
        shouldBeErrorMessage(response.body);
        done();
      })
      .catch(done);
  });
}

/**
 * 要求接口需要测试员访问权限
 * @param {String} uri 统一资源标识符
 * @param {String|Function} url 统一资源定位符
 * @param {String} method http方法
 */
function shouldBeTester(uri, url, method) {
  it('it should failed when the user is not a tester', function (done) {
    const administrator = new Administrator();
    let newAdministrator;
    let response;
    administrator.createRandomAdministrator()
      .then((user) => {
        newAdministrator = user;
        return newAdministrator.login();
      })
      .then(() => request(chai, uri, url, method, newAdministrator.token.accessToken))
      .then((res) => {
        response = res;
        return newAdministrator.logout();
      })
      .then(() => administrator.removeUser(newAdministrator.userId))
      .then(() => {
        response.should.have.status(403);
        shouldBeErrorMessage(response.body);
        done();
      })
      .catch(done);
  });
}

/**
 * 比较两颗树的文件状态
 * @param {Object} actualTree 实际树数据
 * @param {Object} expectedTree 预期树数据
 */
function shouldBeEqualTree(actualTree, expectedTree) {
  Object.keys(expectedTree).forEach((field) => {
    if (field !== 'children') {
      actualTree[field].should.be.eql(expectedTree[field]);
    }
  });
  if (!actualTree.children) {
    expectedTree.should.not.have.property('children');
    return;
  }
  const actualTreeChildrenLen = actualTree.children ? actualTree.children.length : 0;
  const expectedTreeChildrenLen = expectedTree.children ? expectedTree.children.length : 0;
  chai.expect(actualTreeChildrenLen).to.equal(expectedTreeChildrenLen);
  if (expectedTree.children) {
    const expectedChildrenMap = new Map();
    expectedTree.children.forEach((child) => {
      const keyName = child.fileName || child.fullName;
      expectedChildrenMap.set(keyName, child);
    });
    actualTree.children.forEach((actualChild) => {
      const keyName = actualChild.fileName || actualChild.fullName;
      shouldBeEqualTree(actualChild, expectedChildrenMap.get(keyName));
    });
  }
}

function getArrayKeyName(obj, keys) {
  if (!utility.isArrayType(keys)) {
    keys = [keys];
  }
  let keyName = '';
  keys.forEach((key) => {
    if (keyName) {
      return;
    }
    if (key.indexOf('|') >= 0 || key.indexOf('.') >= 0) {
      keyName = key.split('|').map((keyElement) => keyElement.split('.').reduce((pre, objNotation) => (pre ? pre[objNotation] : ''), obj)).join('|');
    } else {
      keyName = obj[keys.find((key) => obj[key] !== undefined)];
    }
  });
  return keyName;
}

/**
 * 比较对象
 * @param {Object} actualTree 实际数据
 * @param {Object} expectedTree 预期数据
 */
function shouldBeEqualObject(actual, expected, keys) {
  Object.keys(expected).forEach((field) => {
    if (utility.isArrayType(expected[field])) {
      expected[field].should.have.length(actual[field].length);
      const expectedChildrenMap = new Map();
      expected[field].forEach((child) => {
        const keyName = getArrayKeyName(child, keys);
        if (keyName) {
          expectedChildrenMap.set(keyName, child);
        }
      });
      actual[field].forEach((actualChild) => {
        const keyName = getArrayKeyName(actualChild, keys);
        if (keyName) {
          shouldBeEqualObject(actualChild, expectedChildrenMap.get(keyName), keys);
        }
      });
    } else if (utility.isObjectType(expected[field])) {
      shouldBeEqualObject(actual[field], expected[field], keys);
    } else {
      (actual[field] === null ? false : actual[field])
        .should.be.eql(expected[field] === null ? false : expected[field]);
    }
  });
}

/**
 * 比较文件树（按文件分类）
 * @param {Object} actualTree 实际树数据
 * @param {Object} expectedTree 预期树数据
 */
function shouldBeEqualFileTree(actualTree, expectedTree) {
  shouldBeEqualObject(actualTree, expectedTree, ['fileName']);
}

/**
 * 比较文件缺陷（按文件分类）
 * @param {Object} actual 实际数据
 * @param {Object} expected 预期数据
 */
function shouldBeEqualFileDefects(actual, expected) {
  shouldBeEqualObject(actual, expected, ['ruleCode']);
}

/**
 * 比较缺陷树（按缺陷分类）
 * @param {Object} actualTree 实际树数据
 * @param {Object} expectedTree 预期树数据
 */
function shouldBeEqualDefectTree(actualTree, expectedTree) {
  shouldBeEqualObject(actualTree, expectedTree, ['ruleSetName', 'level', 'ruleCode']);
}

/**
 * 比较缺陷列表
 * @param {Object} actualList 实际缺陷列表
 * @param {Object} expectedList 预期缺陷列表
 */
function shouldBeEqualDefectsList(actualList, expectedList, keys) {
  shouldBeEqualObject(actualList, expectedList, keys);
}
/**
 * 比较缺陷规则集
 * @param {Object} actualList 实际缺陷列表
 * @param {Object} expectedList 预期缺陷列表
 */
function shouldBeEqualRulesList(actualList, expectedList) {
  shouldBeEqualObject(actualList, expectedList, 'levels|rules|ruleCode');
}

/**
 * 要求树结构与目录结构一致
 * @param {Object} tree 树结构
 * @param {String} dir 目录路径
 * @param {Set} ignoreFiles 要忽略的文件
 */
function shouldCompleteDirectory(tree, dir, ignoreFiles = new Set(['.gitignore', '.git', '.DS_Store', '__MACOSX', '.smartrocket'])) {
  return new Promise((resolve, reject) => {
    shouldBeFileSummaryMessage(tree);
    let children = [];
    fs.stat(dir)
      .then((stats) => {
        if (stats.isDirectory()) {
          tree.kind.should.be.eql(files.kind.directory);
          tree.should.have.property('children');
          tree.children.should.be.a('array');
          children = tree.children.filter((child) => !ignoreFiles.has(child.fileName));
          return fs.readdir(dir);
        }
        tree.kind.should.be.eql(files.kind.file);
        return Promise.resolve([]);
      })
      .then((fileNames) => {
        fileNames = fileNames.filter((fileName) => !ignoreFiles.has(fileName));
        fileNames.forEach((fileName) => {
          children.map(({ fileName }) => fileName).should.contain(fileName);
        });
        const promises = children.map((child) => {
          fileNames.should.contain(child.fileName);
          return shouldCompleteDirectory(child, path.join(dir, child.fileName), ignoreFiles);
        });
        fileNames.length.should.be.eql(children.length);
        return Promise.all(promises);
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 要求filter逻辑完整
 * @param {String|Function} uri 统一资源标识符
 * @param {String|Function} url 统一资源定位符
 * @param {String} method http方法
 * @param {Object|Function} user 当前用户
 * @param {Array|String} sortByKeys 排序键值数组
 */
function shouldCompleteFilter(uri, url, method, user, sortByKeys) {
  const pages = [undefined, 1, 10, 100];
  const perPages = [undefined, 1, 50];
  let sortBys = [undefined];
  if (sortByKeys) {
    if (utility.isArrayType(sortByKeys)) {
      sortBys = sortBys.concat(sortByKeys);
    } else {
      sortBys.push(sortByKeys);
    }
  }
  const orders = ['asc', 'desc'];
  const options = [];
  pages.forEach((page) => {
    perPages.forEach((perPage) => {
      sortBys.forEach((sortBy) => {
        orders.forEach((order) => {
          const opt = {};
          if (page) opt.page = page;
          if (perPage) opt.perPage = perPage;
          if (sortBy) opt.sortBy = sortBy;
          if (order) opt.order = order;
          options.push(opt);
        });
      });
    });
  });
  options.forEach(function (option) {
    it(`it should filter the data by ${JSON.stringify(option)}`, function (done) {
      if (typeof (user) === 'function') { user = user(); }
      user.login()
        .then(() => request(chai, uri, url, method, user.token.accessToken, option))
        .then((res) => {
          let data;
          if (res.body.users) {
            data = res.body.users;
            shouldBeUsersMessage(res.body);
          }
          if (res.body.projects) {
            data = res.body.projects;
            shouldBeProjectsMessage(res.body);
          }
          if (res.body.versions) {
            data = res.body.versions;
            shouldBeProjectVersionsMessage(res.body);
          }
          if (res.body.tasks) {
            data = res.body.tasks;
            shouldBeTasksMessage(res.body);
          }
          if (option.sortBy) {
            if (new Set(['undefined', 'username']).has(option.sortBy)) {
              option.sortBy = 'sortId';
              data.forEach((user) => {
                user.sortId = users.usernameToSortId(user.username);
              });
            }
            data.should.be.sortedBy(option.sortBy, option.order === 'desc');
          }
          done();
        })
        .then()
        .catch(done);
    });
  });
}

/**
 * 要求测试用例数据数据与类型一致
 * @param {Object} data 测试用例数据
 * @param {Object} fileTypes 文件类型系统
 */
function shouldBeCompleteType(data, fileTypes) {
  data['@type'].should.be.a('string');
  fileTypes.should.have.property(data['@type']);
  // data.should.have.property('@value');
  const fileType = fileTypes[data['@type']];
  if (data['@value'] && fileType['@fields']) {
    data['@value'].should.be.a('object');
    fileType['@fields'].should.be.a('object');
    shouldBeCompleteVariables(data['@value'], fileType['@fields'], fileTypes); // eslint-disable-line no-use-before-define
  }
}

/**
 * 要求测试用例数据数据与函数变量一致
 * @param {Object} data 测试用例数据
 * @param {Object} variableFields 函数变量数据
 * @param {Object} fileTypes 文件类型系统
 */
function shouldBeCompleteVariables(data, variableFields, fileTypes) {
  data.should.be.a('object');
  Object.keys(data).forEach((name) => {
    variableFields.should.have.property(name);
    variableFields[name].should.be.a('object');
    const item = data[name];
    item.should.be.a('object');
    if (item['@elementType']) {
      item['@elementType'].should.be.a('string');
      fileTypes.should.have.property(item['@elementType']);
      item.should.have.property('@length');
      if (item['@value']) {
        Object.keys(item['@value']).forEach((index) => {
          index.should.be.a('string');
          Number.parseInt(index, 10).toString().should.be.eql(index);
          const value = item['@value'][index];
          item['@elementType'].should.be.eql(value['@type']);
          shouldBeCompleteType(value, fileTypes);
        });
      }
    } else {
      item.should.have.property('@type');
      item['@type'].should.be.a('string');
      variableFields[name].should.have.property('@type');
      fileTypes.should.have.property(variableFields[name]['@type']);
      const type = fileTypes[variableFields[name]['@type']];
      type.should.be.a('object');
      if (item['@value'] && !utility.isStringType(item['@value'])) {
        item['@value'].should.be.a('object');
        shouldBeCompleteType(item, fileTypes);
      }
    }
  });
}

/**
 * 要求测试用例数据格式与函数变量一致
 * @param {Object} testCase 测试用例数据
 * @param {Object} functionVariables 函数变量数据
 * @param {Object} fileTypes 文件类型系统
 */
function shouldBeCompleteFunctionVariables(testCase, functionVariables, fileTypes) {
  // 指针目标
  if (testCase.malloc) {
    functionVariables.should.have.property('malloc');
    shouldBeCompleteVariables(testCase.malloc, functionVariables.malloc, fileTypes);
  }
  // 输入变量
  const { variables } = testCase;
  if (variables) {
    functionVariables.should.have.property('variables');
    if (variables.global) {
      functionVariables.variables.should.have.property('global');
      shouldBeCompleteVariables(variables.global, functionVariables.variables.global, fileTypes);
    }
    if (variables.params) {
      functionVariables.variables.should.have.property('params');
      Object.keys(variables.params).forEach((name) => {
        variables.params[name]['@type'] = functionVariables.variables.params[name]['@type'];
      });
      shouldBeCompleteVariables(variables.params, functionVariables.variables.params, fileTypes);
    }
  }
  // 桩代码
  if (testCase.stubs) { // eslint-disable-line no-empty
  }

  // 输出
  const { output } = testCase;
  if (output) {
    if (output['%']) {
      functionVariables.should.have.property('output');
      functionVariables.output.should.have.property('%');
      shouldBeCompleteVariables({ '%': output['%'] }, functionVariables.output, fileTypes);
    }
    if (output.global) {
      functionVariables.variables.should.have.property('global');
      shouldBeCompleteVariables(output.global, functionVariables.variables.global, fileTypes);
    }
    if (output.malloc) {
      functionVariables.should.have.property('malloc');
      shouldBeCompleteVariables(output.malloc, functionVariables.malloc, fileTypes);
    }
  }
}

/**
 * 删除空字段
 * @param {Object} obj 数据
 */
function removeEmptyProperty(obj) {
  if (utility.isArrayType(obj)) {
    obj.forEach((item) => {
      removeEmptyProperty(item);
    });
  } else if (utility.isObjectType(obj)) {
    Object.keys(obj).forEach((key) => {
      removeEmptyProperty(obj[key]);
      // 空对象或空数组
      if ((utility.isObjectType(obj[key]) && Object.keys(obj[key]).length === 0)
        || (utility.isArrayType(obj[key]) && obj[key].length === 0)) {
        delete obj[key];
      }
    });
  }
}

/**
 * 检查导出的测试用例内容是否一致
 * @param {Object} tests1 测试用例数据1
 * @param {Object} tests2 测试用例数据2
 */
function ShouldBeSameExportTests(tests1, tests2) {
  Object.keys(tests1).length.should.be.gt(0);
  Object.keys(tests1).length.should.be.eql(Object.keys(tests2).length);
  Object.keys(tests1).forEach((key) => {
    tests2.should.have.property(key);
    const dataSet = new Set();
    tests1[key].forEach(({ data, kind, description }) => {
      removeEmptyProperty(data);
      dataSet.add(JSON.stringify({ data, kind, description }));
    });
    tests2[key].forEach(({ data, kind, description }) => {
      removeEmptyProperty(data);
      dataSet.has(JSON.stringify({ data, kind, description })).should.be.eql(true);
    });
  });
}

/**
 * 判断response信息为项目版本总览类型
 * @param {Object} message
 */
function shouldBeProjectVersionOverviewMessage(message) {
  message.should.be.a('object');
  message.should.have.property('overview');
  message.overview.should.have.property('headerFileCount');
  message.overview.should.have.property('sourceFileCount');
  message.overview.should.have.property('numberOfLines');
  message.overview.should.have.property('functionCount');
  message.overview.should.have.property('testCount');
  message.overview.should.have.property('statementCovered');
  message.overview.should.have.property('statementTotal');
  message.overview.should.have.property('branchCovered');
  message.overview.should.have.property('branchTotal');
  message.overview.should.have.property('mcdcCovered');
  message.overview.should.have.property('mcdcTotal');
  message.should.have.property('testResultOverview');
  message.testResultOverview.should.have.property('manually');
  message.testResultOverview.should.have.property('automatically');
  message.should.have.property('functionCoverage');
}

/**
 * 判断response信息为项目版本总览-文件列表类型
 * @param {Object} message
 */
function shouldBeFilesOverviewMessage(message) {
  message.should.be.a('object');
  message.should.have.property('files');
  message.files.should.be.a('array');
  message.should.have.property('countOverview');
  message.countOverview.should.be.a('array');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
}

/**
 * 判断response信息为项目版本总览-函数列表类型
 * @param {Object} message
 */
function shouldBeFunctionsOverviewMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functions');
  message.functions.should.be.a('array');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
}

/**
 * 判断response信息为项目版本总览-测试用例列表类型
 * @param {Object} message
 */
function shouldBeTestsOverviewMessage(message) {
  message.should.be.a('object');
  message.should.have.property('tests');
  message.tests.should.be.a('array');
  message.should.have.property('pagination');
  message.pagination.should.be.a('object');
  message.pagination.should.have.property('total');
  message.pagination.total.should.be.a('number');
}

function shouldBeInvokeMessage(message) {
  message.should.be.a('object');
  message.should.have.property('functions');
  message.functions.should.be.a('array');
  if (message.functions.length > 0) {
    // message.functions[0].should.have.property('functionId');
    message.functions[0].should.have.property('functionName');
    message.functions[0].should.have.property('mangledId');
    message.functions[0].should.have.property('enableStub');
    message.functions[0].should.have.property('fromFuncId');
    message.functions[0].should.have.property('isSpecialization');
    message.functions[0].should.have.property('returnType');
    message.functions[0].should.have.property('isCreateManual');
    message.functions[0].should.have.property('isDefine');
    message.functions[0].should.have.property('fileId');
    message.functions[0].should.have.property('fileName');
    message.functions[0].should.have.property('filePath');
    message.functions[0].should.have.property('toFuncFileUpdate');
    message.functions[0].should.have.property('files');
    message.functions[0].files.should.be.a('array');
  }
}

function shouldBeEqualInovkes(actual, expected) {
  shouldBeEqualObject(actual, expected, ['functionName', 'path']);
}

function shouldBeSubVersion(actual, expected, isStaticVersion = false) {
  if (isStaticVersion) {
    actual.should.have.property('analyzeTemplate');
    actual.analyzeTemplate.should.be.a('string');
  }
  actual.should.have.property('total');
  actual.total.should.be.a('number');
  actual.total.should.be.eq(expected.total);
  shouldBeEqualTree(actual.tree, expected.tree);
}
function shouldBeVersionConfig(actual, expected) {
  shouldBeEqualObject(actual, expected, []);
}
function shouldBeIncludesAttribute(actual, expected) {
  shouldBeEqualObject(actual, expected, []);
}

function shouldBeEphemeralMessage(message) {
  message.should.be.a('object');
  message.should.have.property('key');
  message.key.should.be.a('string');
  message.should.have.property('data');
  message.data.should.be.a('object');
}

function shouldBeTestFieldConfigMessage(message) {
  message.should.be.a('object');
  message.should.have.property('dataDepth');
  message.dataDepth.should.be.a('number');
  message.should.have.property('dataType');
  message.dataType.should.be.a('array');
  message.should.have.property('variableType');
  message.variableType.should.be.a('array');
  message.should.have.property('displaySetting');
  message.displaySetting.should.be.a('string');
}
/**
 * Export model definition object.
 */
module.exports = {
  should: {
    be: {
      sameExportTests: ShouldBeSameExportTests,

      errorMessage: shouldBeErrorMessage,

      pingMessage: shouldBePingMessage,
      machineCodeMessage: shouldBeMachineCodeMessage,
      licenseMessage: shouldBeLicenseMessage,
      compilationOptionsMessage: shouldCompilationOptionsMessage,

      tokenMessage: shouldBeTokenMessage,

      userMessage: shouldBeUserMessage,
      usersMessage: shouldBeUsersMessage,

      projectMessage: shouldBeProjectMessage,
      projectsMessage: shouldBeProjectsMessage,

      projectVersionMessage: shouldBeProjectVersionMessage,
      projectVersionStatisticsMessage: shouldBeProjectVersionStatisticsMessage,
      projectVersionOverviewMessage: shouldBeProjectVersionOverviewMessage,
      filesOverviewMessage: shouldBeFilesOverviewMessage,
      projectVersionsMessage: shouldBeProjectVersionsMessage,
      functionsOverviewMessage: shouldBeFunctionsOverviewMessage,
      testsOverviewMessage: shouldBeTestsOverviewMessage,

      projectTypesMessage: shouldBeProjectTypesMessage,

      fileMessage: shouldBeFileMessage,
      fileTreeMessage: shouldBeFileTreeMessage,
      fileTreeAllMessage: shouldBeFileTreeAllMessage,
      fileChildrenMessage: shouldBeFileChildrenMessage,
      fileTypesMessage: shouldBeFileTypesMessage,
      fileGlobalsMessage: shouldBeFileGlobalsMessage,
      fileLiteralsMessage: shouldBeFileLiteralsMessage,

      functionMessage: shouldBeFunctionMessage,
      functionsMessage: shouldBeFunctionsMessage,
      functionCFGMessage: shouldBeFunctionCFGMessage,
      functionVariablesMessage: shouldBeFunctionVariablesMessage,
      functionMallocVariableMessage: shouldBeFunctionMallocVariableMessage,
      functionStubPointerTargetMessage: shouldBeStubPointerTargetMessage,
      functionStubMessage: shouldbeStubMessage,
      functionFixedAddressMessage: shouldBeFunctionFixedAddressVariableMessage,
      stubValidParam: shouldbeValidStubParam,

      specializedElement: shouldBeSpecializedElementMessage,
      elementsMessage: shouldBeElementsMessage,
      elementsTree: shouldBeElementsTreeMessage,

      testCaseSummaryMessage: shouldBeTestCaseSummaryMessage,
      testCaseMessage: shouldBeTestCaseMessage,
      testCasesMessage: shouldBeTestCasesMessage,
      exportTestCaseMessage: shouldBeExportTestCaseMessage,
      testCaseGroupMessage: shouldBeTestCaseGroupMessage,

      taskMessage: shouldBeTaskMessage,
      tasksMessage: shouldBeTasksMessage,

      invokeMessage: shouldBeInvokeMessage,

      ephemeralMessage: shouldBeEphemeralMessage,
      testFieldConfigMessage: shouldBeTestFieldConfigMessage,

      user: shouldBeUser,
      administrator: shouldBeAdministrator,
      tester: shouldBeTester,
      includesAttribute: shouldBeIncludesAttribute,

      equal: {
        tree: shouldBeEqualTree,
        fileTree: shouldBeEqualFileTree,
        fileDefects: shouldBeEqualFileDefects,
        defectTree: shouldBeEqualDefectTree,
        defectsList: shouldBeEqualDefectsList,
        invokes: shouldBeEqualInovkes,
        subVersion: shouldBeSubVersion,
        versionConfig: shouldBeVersionConfig,
        ruleSetsList: shouldBeEqualRulesList,
      },
    },
    complete: {
      directory: shouldCompleteDirectory,
      filter: shouldCompleteFilter,
      functionVariables: shouldBeCompleteFunctionVariables,
    },
  },
};
